/*
 * main.cpp
 * * ESP32-S3 子程序调度器 (Program Loader)
 * * I2C 引脚:
 * - SDA: GPIO 18
 * - SCL: GPIO 19
 */

// === 1. 包含依赖 ===

#include <Arduino.h>

// 核心库
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h> 
#include <Preferences.h>      

// 包含抽象接口
#include "ISubProgram.h"

// 包含您将要编写的各个子程序的头文件
#include "ProgramA.h"
// #include "ProgramB.h"
// #include "ProgramC.h" 


// === 2. 硬件与常量定义 ===

// [已修改] 定义 I2C 引脚
#define I2C_SDA 18
#define I2C_SCL 19

// OLED 定义
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define OLED_RESET    -1       
#define OLED_I2C_ADDR 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// 按钮定义 (请根据您的接线修改)
#define BTN_MENU   0  // BOOT 键, 仅在启动时检查
#define BTN_UP     33 // 菜单 - 上
#define BTN_DOWN   32 // 菜单 - 下
#define BTN_SELECT 35 // 菜单 - 选择

const long DEBOUNCE_DELAY = 50; // 50ms 按钮去抖

// NVS 存储键
const char* NVS_NAMESPACE = "mother_prog";
const char* NVS_KEY_LAST_PROG = "last_prog_id";


// === 3. 全局状态变量 ===
enum SystemState {
    STATE_MENU,
    STATE_RUNNING_SUB_PROGRAM
};
SystemState currentState;           
ISubProgram* currentProgram = nullptr; 
Preferences preferences;            

// 菜单状态
int menuCurrentSelection = 0;
bool menuNeedsRedraw = true;
unsigned long lastButtonTime = 0;


// === 4. 程序注册表 (核心) ===

// 步骤 1: 定义工厂函数
ISubProgram* createProgramA() { return new ProgramA(); }
// ISubProgram* createProgramB() { return new ProgramB(); }
// ISubProgram* createProgramC() { return new ProgramC(); }

// 步骤 2: 定义描述符结构体
struct ProgramDescriptor {
    const char* menuName;                 
    ISubProgram* (*factory)();           
};

// 步骤 3: 填充注册表
ProgramDescriptor programRegistry[] = {
    {"Run Task A", createProgramA},
    // {"Run Task B", createProgramB},
    // {"Run Task C", createProgramC},
};

const int programCount = sizeof(programRegistry) / sizeof(programRegistry[0]);


// === 5. 函数前向声明 ===
void loadAndRunProgram(int index);
void cleanupCurrentProgram();
void runMenuLogic();
void drawMenu();


// === 6. Arduino 主函数: setup() ===
void setup() {
    Serial.begin(115200);
    Serial.println("Mother Program Booting...");

    // 1. 初始化按钮
    pinMode(BTN_MENU, INPUT_PULLUP);
    pinMode(BTN_UP, INPUT_PULLUP);
    pinMode(BTN_DOWN, INPUT_PULLUP);
    pinMode(BTN_SELECT, INPUT_PULLUP);
    
    // 2. 初始化 I2C 和 OLED
    // 显式指定 SDA 和 SCL 引脚
    // 必须在 display.begin() 之前调用
    Wire.begin(I2C_SDA, I2C_SCL); 
    
    if(!display.begin(SSD1306_SWITCHCAPVCC, OLED_I2C_ADDR)) { 
        Serial.println(F("SSD1306 allocation failed"));
        for(;;); 
    }
    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0,10);
    display.print("Booting...");
    display.display();
    delay(500);

    // 3. 初始化 NVS
    preferences.begin(NVS_NAMESPACE, false); 

    // 4. 核心启动逻辑: 检查 BOOT 键 (BTN_MENU)
    if (digitalRead(BTN_MENU) == LOW) {
        // 模式: 进入菜单
        Serial.println("BTN_MENU pressed. Entering menu.");
        currentState = STATE_MENU;
        preferences.putUInt(NVS_KEY_LAST_PROG, 999);
        menuNeedsRedraw = true;
    } else {
        // 模式: 自动加载
        Serial.println("Normal boot. Loading last program.");
        unsigned int lastProgID = preferences.getUInt(NVS_KEY_LAST_PROG, 999);

        if (lastProgID < programCount) {
            loadAndRunProgram(lastProgID);
        } else {
            Serial.println("No valid last program. Entering menu.");
            currentState = STATE_MENU;
            menuNeedsRedraw = true;
        }
    }
}


// === 7. Arduino 主函数: loop() ===
void loop() {
    // 主状态机调度器
    switch (currentState) {
        case STATE_MENU:
            runMenuLogic();
            break;

        case STATE_RUNNING_SUB_PROGRAM:
            if (currentProgram != nullptr) {
                currentProgram->sub_loop(); 
            }
            break;
    }
}


// === 8. 辅助函数实现 ===

void cleanupCurrentProgram() {
    if (currentProgram != nullptr) {
        Serial.println("Cleaning up old program...");
        currentProgram->sub_cleanup();
        delete currentProgram;
        currentProgram = nullptr;
    }
}

void loadAndRunProgram(int index) {
    if (index < 0 || index >= programCount) {
        Serial.printf("Error: Invalid program index %d\n", index);
        return;
    }
    cleanupCurrentProgram();

    display.clearDisplay();
    display.setCursor(0, 10);
    display.print("Loading: ");
    display.print(programRegistry[index].menuName);
    display.display();
    Serial.print("Loading program: ");
    Serial.println(programRegistry[index].menuName);

    currentProgram = programRegistry[index].factory();
    
    if (currentProgram == nullptr) {
        Serial.println("Error: Factory returned nullptr!");
        return;
    }

    currentProgram->sub_setup();
    preferences.putUInt(NVS_KEY_LAST_PROG, index);
    currentState = STATE_RUNNING_SUB_PROGRAM;
}

void runMenuLogic() {
    if (millis() - lastButtonTime < DEBOUNCE_DELAY) {
        return;
    }
    bool buttonPressed = false;
    
    if (digitalRead(BTN_DOWN) == LOW) {
        menuCurrentSelection = (menuCurrentSelection + 1) % programCount;
        menuNeedsRedraw = true;
        buttonPressed = true;
    } 
    else if (digitalRead(BTN_UP) == LOW) {
        menuCurrentSelection = (menuCurrentSelection - 1 + programCount) % programCount;
        menuNeedsRedraw = true;
        buttonPressed = true;
    } 
    else if (digitalRead(BTN_SELECT) == LOW) {
        buttonPressed = true;
        loadAndRunProgram(menuCurrentSelection);
        return; 
    }

    if (buttonPressed) {
        lastButtonTime = millis();
    }
    if (menuNeedsRedraw) {
        drawMenu();
        menuNeedsRedraw = false;
    }
}

void drawMenu() {
    display.clearDisplay();
    display.setTextSize(1);
    
    int prevIndex = (menuCurrentSelection - 1 + programCount) % programCount;
    int nextIndex = (menuCurrentSelection + 1) % programCount;

    if (programCount > 1) {
        display.setCursor(5, 0); 
        display.print(programRegistry[prevIndex].menuName);
    }

    display.setCursor(0, 12); 
    display.print("> "); 
    display.print(programRegistry[menuCurrentSelection].menuName);
    display.print(" <");

    if (programCount > 2) { 
        display.setCursor(5, 24); 
        display.print(programRegistry[nextIndex].menuName);
    }
    display.display();
}